/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=8 sts=2 et sw=2 tw=80: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. *//* * Base class for the XML and HTML content sinks, which construct a * DOM based on information from the parser. */#include"nsContentSink.h"#include"nsIDocument.h"#include"nsIDOMDocument.h"#include"mozilla/css/Loader.h"#include"mozilla/dom/SRILogHelper.h"#include"nsStyleLinkElement.h"#include"nsIDocShell.h"#include"nsILoadContext.h"#include"nsCPrefetchService.h"#include"nsIURI.h"#include"nsNetUtil.h"#include"nsIMIMEHeaderParam.h"#include"nsIProtocolHandler.h"#include"nsIHttpChannel.h"#include"nsIContent.h"#include"nsIPresShell.h"#include"nsPresContext.h"#include"nsViewManager.h"#include"nsIAtom.h"#include"nsGkAtoms.h"#include"nsNetCID.h"#include"nsIOfflineCacheUpdate.h"#include"nsIApplicationCache.h"#include"nsIApplicationCacheContainer.h"#include"nsIApplicationCacheChannel.h"#include"nsIScriptSecurityManager.h"#include"nsICookieService.h"#include"nsContentUtils.h"#include"nsNodeInfoManager.h"#include"nsIAppShell.h"#include"nsIWidget.h"#include"nsWidgetsCID.h"#include"nsIDOMNode.h"#include"mozAutoDocUpdate.h"#include"nsIWebNavigation.h"#include"nsGenericHTMLElement.h"#include"nsHTMLDNSPrefetch.h"#include"nsIObserverService.h"#include"mozilla/Preferences.h"#include"mozilla/dom/ScriptLoader.h"#include"nsParserConstants.h"#include"nsSandboxFlags.h"usingnamespacemozilla;LazyLogModulegContentSinkLogModuleInfo("nscontentsink");NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink)NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink)NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink)NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)NS_INTERFACE_MAP_ENTRY(nsITimerCallback)NS_INTERFACE_MAP_ENTRY(nsINamed)NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIDocumentObserver)NS_INTERFACE_MAP_ENDNS_IMPL_CYCLE_COLLECTION_CLASS(nsContentSink)NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsContentSink)if(tmp->mDocument){tmp->mDocument->RemoveObserver(tmp);}NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager)NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader)NS_IMPL_CYCLE_COLLECTION_UNLINK_ENDNS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsContentSink)NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)NS_IMPL_CYCLE_COLLECTION_TRAVERSE_ENDnsContentSink::nsContentSink():mBackoffCount(0),mLastNotificationTime(0),mBeganUpdate(0),mLayoutStarted(0),mDynamicLowerValue(0),mParsing(0),mDroppedTimer(0),mDeferredLayoutStart(0),mDeferredFlushTags(0),mIsDocumentObserver(0),mRunsToCompletion(0),mDeflectedCount(0),mHasPendingEvent(false),mCurrentParseEndTime(0),mBeginLoadTime(0),mLastSampledUserEventTime(0),mInMonolithicContainer(0),mInNotification(0),mUpdatesInNotification(0),mPendingSheetCount(0){NS_ASSERTION(!mLayoutStarted,"What?");NS_ASSERTION(!mDynamicLowerValue,"What?");NS_ASSERTION(!mParsing,"What?");NS_ASSERTION(mLastSampledUserEventTime==0,"What?");NS_ASSERTION(mDeflectedCount==0,"What?");NS_ASSERTION(!mDroppedTimer,"What?");NS_ASSERTION(mInMonolithicContainer==0,"What?");NS_ASSERTION(mInNotification==0,"What?");NS_ASSERTION(!mDeferredLayoutStart,"What?");}nsContentSink::~nsContentSink(){if(mDocument){// Remove ourselves just to be safe, though we really should have// been removed in DidBuildModel if everything worked right.mDocument->RemoveObserver(this);}}boolnsContentSink::sNotifyOnTimer;int32_tnsContentSink::sBackoffCount;int32_tnsContentSink::sNotificationInterval;int32_tnsContentSink::sInteractiveDeflectCount;int32_tnsContentSink::sPerfDeflectCount;int32_tnsContentSink::sPendingEventMode;int32_tnsContentSink::sEventProbeRate;int32_tnsContentSink::sInteractiveParseTime;int32_tnsContentSink::sPerfParseTime;int32_tnsContentSink::sInteractiveTime;int32_tnsContentSink::sInitialPerfTime;int32_tnsContentSink::sEnablePerfMode;voidnsContentSink::InitializeStatics(){Preferences::AddBoolVarCache(&sNotifyOnTimer,"content.notify.ontimer",true);// -1 means never.Preferences::AddIntVarCache(&sBackoffCount,"content.notify.backoffcount",-1);// The gNotificationInterval has a dramatic effect on how long it// takes to initially display content for slow connections.// The current value provides good// incremental display of content without causing an increase// in page load time. If this value is set below 1/10 of second// it starts to impact page load performance.// see bugzilla bug 72138 for more info.Preferences::AddIntVarCache(&sNotificationInterval,"content.notify.interval",120000);Preferences::AddIntVarCache(&sInteractiveDeflectCount,"content.sink.interactive_deflect_count",0);Preferences::AddIntVarCache(&sPerfDeflectCount,"content.sink.perf_deflect_count",200);Preferences::AddIntVarCache(&sPendingEventMode,"content.sink.pending_event_mode",1);Preferences::AddIntVarCache(&sEventProbeRate,"content.sink.event_probe_rate",1);Preferences::AddIntVarCache(&sInteractiveParseTime,"content.sink.interactive_parse_time",3000);Preferences::AddIntVarCache(&sPerfParseTime,"content.sink.perf_parse_time",360000);Preferences::AddIntVarCache(&sInteractiveTime,"content.sink.interactive_time",750000);Preferences::AddIntVarCache(&sInitialPerfTime,"content.sink.initial_perf_time",2000000);Preferences::AddIntVarCache(&sEnablePerfMode,"content.sink.enable_perf_mode",0);}nsresultnsContentSink::Init(nsIDocument*aDoc,nsIURI*aURI,nsISupports*aContainer,nsIChannel*aChannel){NS_PRECONDITION(aDoc,"null ptr");NS_PRECONDITION(aURI,"null ptr");if(!aDoc||!aURI){returnNS_ERROR_NULL_POINTER;}mDocument=aDoc;mDocumentURI=aURI;mDocShell=do_QueryInterface(aContainer);mScriptLoader=mDocument->ScriptLoader();if(!mRunsToCompletion){if(mDocShell){uint32_tloadType=0;mDocShell->GetLoadType(&loadType);mDocument->SetChangeScrollPosWhenScrollingToRef((loadType&nsIDocShell::LOAD_CMD_HISTORY)==0);}ProcessHTTPHeaders(aChannel);}mCSSLoader=aDoc->CSSLoader();mNodeInfoManager=aDoc->NodeInfoManager();mBackoffCount=sBackoffCount;if(sEnablePerfMode!=0){mDynamicLowerValue=sEnablePerfMode==1;FavorPerformanceHint(!mDynamicLowerValue,0);}returnNS_OK;}NS_IMETHODIMPnsContentSink::StyleSheetLoaded(StyleSheet*aSheet,boolaWasAlternate,nsresultaStatus){NS_ASSERTION(!mRunsToCompletion,"How come a fragment parser observed sheets?");if(!aWasAlternate){NS_ASSERTION(mPendingSheetCount>0,"How'd that happen?");--mPendingSheetCount;if(mPendingSheetCount==0&&(mDeferredLayoutStart||mDeferredFlushTags)){if(mDeferredFlushTags){FlushTags();}if(mDeferredLayoutStart){// We might not have really started layout, since this sheet was still// loading. Do it now. Probably doesn't matter whether we do this// before or after we unblock scripts, but before feels saner. Note// that if mDeferredLayoutStart is true, that means any subclass// StartLayout() stuff that needs to happen has already happened, so we// don't need to worry about it.StartLayout(false);}// Go ahead and try to scroll to our ref if we have oneScrollToRef();}mScriptLoader->RemoveParserBlockingScriptExecutionBlocker();}returnNS_OK;}nsresultnsContentSink::ProcessHTTPHeaders(nsIChannel*aChannel){nsCOMPtr<nsIHttpChannel>httpchannel(do_QueryInterface(aChannel));if(!httpchannel){returnNS_OK;}// Note that the only header we care about is the "link" header, since we// have all the infrastructure for kicking off stylesheet loads.nsAutoCStringlinkHeader;nsresultrv=httpchannel->GetResponseHeader(NS_LITERAL_CSTRING("link"),linkHeader);if(NS_SUCCEEDED(rv)&&!linkHeader.IsEmpty()){mDocument->SetHeaderData(nsGkAtoms::link,NS_ConvertASCIItoUTF16(linkHeader));NS_ASSERTION(!mProcessLinkHeaderEvent.get(),"Already dispatched an event?");mProcessLinkHeaderEvent=NewNonOwningRunnableMethod("nsContentSink::DoProcessLinkHeader",this,&nsContentSink::DoProcessLinkHeader);rv=NS_DispatchToCurrentThread(mProcessLinkHeaderEvent.get());if(NS_FAILED(rv)){mProcessLinkHeaderEvent.Forget();}}returnNS_OK;}nsresultnsContentSink::ProcessHeaderData(nsIAtom*aHeader,constnsAString&aValue,nsIContent*aContent){nsresultrv=NS_OK;// necko doesn't process headers coming in from the parsermDocument->SetHeaderData(aHeader,aValue);if(aHeader==nsGkAtoms::setcookie){// Note: Necko already handles cookies set via the channel. We can't just// call SetCookie on the channel because we want to do some security checks// here.nsCOMPtr<nsICookieService>cookieServ=do_GetService(NS_COOKIESERVICE_CONTRACTID,&rv);if(NS_FAILED(rv)){returnrv;}// Get a URI from the document principal// We use the original codebase in case the codebase was changed// by SetDomain// Note that a non-codebase principal (eg the system principal) will return// a null URI.nsCOMPtr<nsIURI>codebaseURI;rv=mDocument->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));NS_ENSURE_TRUE(codebaseURI,rv);nsCOMPtr<nsIChannel>channel;if(mParser){mParser->GetChannel(getter_AddRefs(channel));}rv=cookieServ->SetCookieString(codebaseURI,nullptr,NS_ConvertUTF16toUTF8(aValue).get(),channel);if(NS_FAILED(rv)){returnrv;}}returnrv;}voidnsContentSink::DoProcessLinkHeader(){nsAutoStringvalue;mDocument->GetHeaderData(nsGkAtoms::link,value);ProcessLinkHeader(value);}// check whether the Link header field applies to the context resource// see <http://tools.ietf.org/html/rfc5988#section-5.2>boolnsContentSink::LinkContextIsOurDocument(constnsAString&aAnchor){if(aAnchor.IsEmpty()){// anchor parameter not present or empty -> same document referencereturntrue;}nsIURI*docUri=mDocument->GetDocumentURI();// the document URI might contain a fragment identifier ("#...')// we want to ignore that because it's invisible to the server// and just affects the local interpretation in the recipientnsCOMPtr<nsIURI>contextUri;nsresultrv=docUri->CloneIgnoringRef(getter_AddRefs(contextUri));if(NS_FAILED(rv)){// copying failedreturnfalse;}// resolve anchor against context nsCOMPtr<nsIURI>resolvedUri;rv=NS_NewURI(getter_AddRefs(resolvedUri),aAnchor,nullptr,contextUri);if(NS_FAILED(rv)){// resolving failedreturnfalse;}boolsame;rv=contextUri->Equals(resolvedUri,&same);if(NS_FAILED(rv)){// comparison failedreturnfalse;}returnsame;}// Decode a parameter value using the encoding defined in RFC 5987 (in place)//// charset "'" [ language ] "'" value-chars//// returns true when decoding happened successfully (otherwise leaves// passed value alone)boolnsContentSink::Decode5987Format(nsAString&aEncoded){nsresultrv;nsCOMPtr<nsIMIMEHeaderParam>mimehdrpar=do_GetService(NS_MIMEHEADERPARAM_CONTRACTID,&rv);if(NS_FAILED(rv))returnfalse;nsAutoCStringasciiValue;constchar16_t*encstart=aEncoded.BeginReading();constchar16_t*encend=aEncoded.EndReading();// create a plain ASCII string, aborting if we can't do that// converted form is always shorter than inputwhile(encstart!=encend){if(*encstart>0&&*encstart<128){asciiValue.Append((char)*encstart);}else{returnfalse;}encstart++;}nsAutoStringdecoded;nsAutoCStringlanguage;rv=mimehdrpar->DecodeRFC5987Param(asciiValue,language,decoded);if(NS_FAILED(rv))returnfalse;aEncoded=decoded;returntrue;}nsresultnsContentSink::ProcessLinkHeader(constnsAString&aLinkData){nsresultrv=NS_OK;// keep track where we are within the header fieldboolseenParameters=false;// parse link content and call process style linknsAutoStringhref;nsAutoStringrel;nsAutoStringtitle;nsAutoStringtitleStar;nsAutoStringtype;nsAutoStringmedia;nsAutoStringanchor;nsAutoStringcrossOrigin;crossOrigin.SetIsVoid(true);// copy to work buffernsAutoStringstringList(aLinkData);// put an extra null at the endstringList.Append(kNullCh);char16_t*start=stringList.BeginWriting();char16_t*end=start;char16_t*last=start;char16_tendCh;while(*start!=kNullCh){// skip leading spacewhile((*start!=kNullCh)&&nsCRT::IsAsciiSpace(*start)){++start;}end=start;last=end-1;boolwasQuotedString=false;// look for semicolon or commawhile(*end!=kNullCh&&*end!=kSemicolon&&*end!=kComma){char16_tch=*end;if(ch==kQuote||ch==kLessThan){// quoted stringchar16_tquote=ch;if(quote==kLessThan){quote=kGreaterThan;}wasQuotedString=(ch==kQuote);char16_t*closeQuote=(end+1);// seek closing quotewhile(*closeQuote!=kNullCh&"e!=*closeQuote){// in quoted-string, "\" is an escape characterif(wasQuotedString&&*closeQuote==kBackSlash&&*(closeQuote+1)!=kNullCh){++closeQuote;}++closeQuote;}if(quote==*closeQuote){// found closer// skip to close quoteend=closeQuote;last=end-1;ch=*(end+1);if(ch!=kNullCh&&ch!=kSemicolon&&ch!=kComma){// end string here*(++end)=kNullCh;ch=*(end+1);// keep going until semi or commawhile(ch!=kNullCh&&ch!=kSemicolon&&ch!=kComma){++end;ch=*(end+1);}}}}++end;++last;}endCh=*end;// end string here*end=kNullCh;if(start<end){if((*start==kLessThan)&&(*last==kGreaterThan)){*last=kNullCh;// first instance of <...> wins// also, do not allow hrefs after the first param was seenif(href.IsEmpty()&&!seenParameters){href=(start+1);href.StripWhitespace();}}else{char16_t*equals=start;seenParameters=true;while((*equals!=kNullCh)&&(*equals!=kEqual)){equals++;}if(*equals!=kNullCh){*equals=kNullCh;nsAutoStringattr(start);attr.StripWhitespace();char16_t*value=++equals;while(nsCRT::IsAsciiSpace(*value)){value++;}if((*value==kQuote)&&(*value==*last)){*last=kNullCh;value++;}if(wasQuotedString){// unescape in-placechar16_t*unescaped=value;char16_t*src=value;while(*src!=kNullCh){if(*src==kBackSlash&&*(src+1)!=kNullCh){src++;}*unescaped++=*src++;}*unescaped=kNullCh;}if(attr.LowerCaseEqualsLiteral("rel")){if(rel.IsEmpty()){rel=value;rel.CompressWhitespace();}}elseif(attr.LowerCaseEqualsLiteral("title")){if(title.IsEmpty()){title=value;title.CompressWhitespace();}}elseif(attr.LowerCaseEqualsLiteral("title*")){if(titleStar.IsEmpty()&&!wasQuotedString){// RFC 5987 encoding; uses token format only, so skip if we get// here with a quoted-stringnsAutoStringtmp;tmp=value;if(Decode5987Format(tmp)){titleStar=tmp;titleStar.CompressWhitespace();}else{// header value did not parse, throw it awaytitleStar.Truncate();}}}elseif(attr.LowerCaseEqualsLiteral("type")){if(type.IsEmpty()){type=value;type.StripWhitespace();}}elseif(attr.LowerCaseEqualsLiteral("media")){if(media.IsEmpty()){media=value;// The HTML5 spec is formulated in terms of the CSS3 spec,// which specifies that media queries are case insensitive.nsContentUtils::ASCIIToLower(media);}}elseif(attr.LowerCaseEqualsLiteral("anchor")){if(anchor.IsEmpty()){anchor=value;anchor.StripWhitespace();}}elseif(attr.LowerCaseEqualsLiteral("crossorigin")){if(crossOrigin.IsVoid()){crossOrigin.SetIsVoid(false);crossOrigin=value;crossOrigin.StripWhitespace();}}}}}if(endCh==kComma){// hit a comma, process what we've got so farhref.Trim(" \t\n\r\f");// trim HTML5 whitespaceif(!href.IsEmpty()&&!rel.IsEmpty()){rv=ProcessLink(anchor,href,rel,// prefer RFC 5987 variant over non-I18zed versiontitleStar.IsEmpty()?title:titleStar,type,media,crossOrigin);}href.Truncate();rel.Truncate();title.Truncate();type.Truncate();media.Truncate();anchor.Truncate();crossOrigin.SetIsVoid(true);seenParameters=false;}start=++end;}href.Trim(" \t\n\r\f");// trim HTML5 whitespaceif(!href.IsEmpty()&&!rel.IsEmpty()){rv=ProcessLink(anchor,href,rel,// prefer RFC 5987 variant over non-I18zed versiontitleStar.IsEmpty()?title:titleStar,type,media,crossOrigin);}returnrv;}nsresultnsContentSink::ProcessLink(constnsAString&aAnchor,constnsAString&aHref,constnsAString&aRel,constnsAString&aTitle,constnsAString&aType,constnsAString&aMedia,constnsAString&aCrossOrigin){uint32_tlinkTypes=nsStyleLinkElement::ParseLinkTypes(aRel);// The link relation may apply to a different resource, specified// in the anchor parameter. For the link relations supported so far,// we simply abort if the link applies to a resource different to the// one we've loadedif(!LinkContextIsOurDocument(aAnchor)){returnNS_OK;}if(!nsContentUtils::PrefetchEnabled(mDocShell)){returnNS_OK;}boolhasPrefetch=linkTypes&nsStyleLinkElement::ePREFETCH;// prefetch href if relation is "next" or "prefetch"if(hasPrefetch||(linkTypes&nsStyleLinkElement::eNEXT)){PrefetchHref(aHref,mDocument,hasPrefetch);}if(linkTypes&nsStyleLinkElement::ePRERENDER){nsCOMPtr<nsIURI>href;nsresultrv=NS_NewURI(getter_AddRefs(href),aHref);if(NS_SUCCEEDED(rv)){mDocument->PrerenderHref(href);}}if(!aHref.IsEmpty()&&(linkTypes&nsStyleLinkElement::eDNS_PREFETCH)){PrefetchDNS(aHref);}if(!aHref.IsEmpty()&&(linkTypes&nsStyleLinkElement::ePRECONNECT)){Preconnect(aHref,aCrossOrigin);}// is it a stylesheet link?if(!(linkTypes&nsStyleLinkElement::eSTYLESHEET)){returnNS_OK;}boolisAlternate=linkTypes&nsStyleLinkElement::eALTERNATE;returnProcessStyleLink(nullptr,aHref,isAlternate,aTitle,aType,aMedia);}nsresultnsContentSink::ProcessStyleLink(nsIContent*aElement,constnsAString&aHref,boolaAlternate,constnsAString&aTitle,constnsAString&aType,constnsAString&aMedia){if(aAlternate&&aTitle.IsEmpty()){// alternates must have title return without error, for nowreturnNS_OK;}nsAutoStringmimeType;nsAutoStringparams;nsContentUtils::SplitMimeType(aType,mimeType,params);// see bug 18817if(!mimeType.IsEmpty()&&!mimeType.LowerCaseEqualsLiteral("text/css")){// Unknown stylesheet languagereturnNS_OK;}nsCOMPtr<nsIURI>url;nsresultrv=NS_NewURI(getter_AddRefs(url),aHref,nullptr,mDocument->GetDocBaseURI());if(NS_FAILED(rv)){// The URI is bad, move along, don't propagate the error (for now)returnNS_OK;}NS_ASSERTION(!aElement||aElement->NodeType()==nsIDOMNode::PROCESSING_INSTRUCTION_NODE,"We only expect processing instructions here");nsAutoStringintegrity;if(aElement){aElement->GetAttr(kNameSpaceID_None,nsGkAtoms::integrity,integrity);}if(!integrity.IsEmpty()){MOZ_LOG(dom::SRILogHelper::GetSriLog(),mozilla::LogLevel::Debug,("nsContentSink::ProcessStyleLink, integrity=%s",NS_ConvertUTF16toUTF8(integrity).get()));}// If this is a fragment parser, we don't want to observe.// We don't support CORS for processing instructionsboolisAlternate;rv=mCSSLoader->LoadStyleLink(aElement,url,aTitle,aMedia,aAlternate,CORS_NONE,mDocument->GetReferrerPolicy(),integrity,mRunsToCompletion?nullptr:this,&isAlternate);NS_ENSURE_SUCCESS(rv,rv);if(!isAlternate&&!mRunsToCompletion){++mPendingSheetCount;mScriptLoader->AddParserBlockingScriptExecutionBlocker();}returnNS_OK;}nsresultnsContentSink::ProcessMETATag(nsIContent*aContent){NS_ASSERTION(aContent,"missing meta-element");nsresultrv=NS_OK;// set any HTTP-EQUIV data into document's header data as well as urlnsAutoStringheader;aContent->GetAttr(kNameSpaceID_None,nsGkAtoms::httpEquiv,header);if(!header.IsEmpty()){// Ignore META REFRESH when document is sandboxed from automatic features.nsContentUtils::ASCIIToLower(header);if(nsGkAtoms::refresh->Equals(header)&&(mDocument->GetSandboxFlags()&SANDBOXED_AUTOMATIC_FEATURES)){returnNS_OK;}nsAutoStringresult;aContent->GetAttr(kNameSpaceID_None,nsGkAtoms::content,result);if(!result.IsEmpty()){nsCOMPtr<nsIAtom>fieldAtom(NS_Atomize(header));rv=ProcessHeaderData(fieldAtom,result,aContent);}}NS_ENSURE_SUCCESS(rv,rv);if(aContent->AttrValueIs(kNameSpaceID_None,nsGkAtoms::name,nsGkAtoms::handheldFriendly,eIgnoreCase)){nsAutoStringresult;aContent->GetAttr(kNameSpaceID_None,nsGkAtoms::content,result);if(!result.IsEmpty()){nsContentUtils::ASCIIToLower(result);mDocument->SetHeaderData(nsGkAtoms::handheldFriendly,result);}}returnrv;}voidnsContentSink::PrefetchHref(constnsAString&aHref,nsINode*aSource,boolaExplicit){nsCOMPtr<nsIPrefetchService>prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));if(prefetchService){// construct URI using document charsetautoencoding=mDocument->GetDocumentCharacterSet();nsCOMPtr<nsIURI>uri;NS_NewURI(getter_AddRefs(uri),aHref,encoding,mDocument->GetDocBaseURI());if(uri){nsCOMPtr<nsIDOMNode>domNode=do_QueryInterface(aSource);prefetchService->PrefetchURI(uri,mDocumentURI,domNode,aExplicit);}}}voidnsContentSink::PrefetchDNS(constnsAString&aHref){nsAutoStringhostname;if(StringBeginsWith(aHref,NS_LITERAL_STRING("//"))){hostname=Substring(aHref,2);}else{nsCOMPtr<nsIURI>uri;NS_NewURI(getter_AddRefs(uri),aHref);if(!uri){return;}nsresultrv;boolisLocalResource=false;rv=NS_URIChainHasFlags(uri,nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,&isLocalResource);if(NS_SUCCEEDED(rv)&&!isLocalResource){nsAutoCStringhost;uri->GetHost(host);CopyUTF8toUTF16(host,hostname);}}if(!hostname.IsEmpty()&&nsHTMLDNSPrefetch::IsAllowed(mDocument)){nsHTMLDNSPrefetch::PrefetchLow(hostname,mDocument->NodePrincipal()->OriginAttributesRef());}}voidnsContentSink::Preconnect(constnsAString&aHref,constnsAString&aCrossOrigin){// construct URI using document charsetautoencoding=mDocument->GetDocumentCharacterSet();nsCOMPtr<nsIURI>uri;NS_NewURI(getter_AddRefs(uri),aHref,encoding,mDocument->GetDocBaseURI());if(uri&&mDocument){mDocument->MaybePreconnect(uri,dom::Element::StringToCORSMode(aCrossOrigin));}}nsresultnsContentSink::SelectDocAppCache(nsIApplicationCache*aLoadApplicationCache,nsIURI*aManifestURI,boolaFetchedWithHTTPGetOrEquiv,CacheSelectionAction*aAction){nsresultrv;*aAction=CACHE_SELECTION_NONE;nsCOMPtr<nsIApplicationCacheContainer>applicationCacheDocument=do_QueryInterface(mDocument);NS_ASSERTION(applicationCacheDocument,"mDocument must implement nsIApplicationCacheContainer.");if(aLoadApplicationCache){nsCOMPtr<nsIURI>groupURI;rv=aLoadApplicationCache->GetManifestURI(getter_AddRefs(groupURI));NS_ENSURE_SUCCESS(rv,rv);boolequal=false;rv=groupURI->Equals(aManifestURI,&equal);NS_ENSURE_SUCCESS(rv,rv);if(!equal){// This is a foreign entry, force a reload to avoid loading the foreign// entry. The entry will be marked as foreign to avoid loading it again.*aAction=CACHE_SELECTION_RELOAD;}else{// The http manifest attribute URI is equal to the manifest URI of// the cache the document was loaded from - associate the document with// that cache and invoke the cache update process.#ifdef DEBUGnsAutoCStringdocURISpec,clientID;mDocumentURI->GetAsciiSpec(docURISpec);aLoadApplicationCache->GetClientID(clientID);SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),SINK_TRACE_CALLS,("Selection: assigning app cache %s to document %s",clientID.get(),docURISpec.get()));#endifrv=applicationCacheDocument->SetApplicationCache(aLoadApplicationCache);NS_ENSURE_SUCCESS(rv,rv);// Document will be added as implicit entry to the cache as part of// the update process.*aAction=CACHE_SELECTION_UPDATE;}}else{// The document was not loaded from an application cache// Here we know the manifest has the same origin as the// document. There is call to CheckMayLoad() on it above.if(!aFetchedWithHTTPGetOrEquiv){// The document was not loaded using HTTP GET or equivalent// method. The spec says to run the cache selection algorithm w/o// the manifest specified.*aAction=CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST;}else{// Always do an update in this case*aAction=CACHE_SELECTION_UPDATE;}}returnNS_OK;}nsresultnsContentSink::SelectDocAppCacheNoManifest(nsIApplicationCache*aLoadApplicationCache,nsIURI**aManifestURI,CacheSelectionAction*aAction){*aManifestURI=nullptr;*aAction=CACHE_SELECTION_NONE;nsresultrv;if(aLoadApplicationCache){// The document was loaded from an application cache, use that// application cache as the document's application cache.nsCOMPtr<nsIApplicationCacheContainer>applicationCacheDocument=do_QueryInterface(mDocument);NS_ASSERTION(applicationCacheDocument,"mDocument must implement nsIApplicationCacheContainer.");#ifdef DEBUGnsAutoCStringdocURISpec,clientID;mDocumentURI->GetAsciiSpec(docURISpec);aLoadApplicationCache->GetClientID(clientID);SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),SINK_TRACE_CALLS,("Selection, no manifest: assigning app cache %s to document %s",clientID.get(),docURISpec.get()));#endifrv=applicationCacheDocument->SetApplicationCache(aLoadApplicationCache);NS_ENSURE_SUCCESS(rv,rv);// Return the uri and invoke the update process for the selected// application cache.rv=aLoadApplicationCache->GetManifestURI(aManifestURI);NS_ENSURE_SUCCESS(rv,rv);*aAction=CACHE_SELECTION_UPDATE;}returnNS_OK;}voidnsContentSink::ProcessOfflineManifest(nsIContent*aElement){// Only check the manifest for root document nodes.if(aElement!=mDocument->GetRootElement()){return;}// Don't bother processing offline manifest for documents// without a docshellif(!mDocShell){return;}// Check for a manifest= attribute.nsAutoStringmanifestSpec;aElement->GetAttr(kNameSpaceID_None,nsGkAtoms::manifest,manifestSpec);ProcessOfflineManifest(manifestSpec);}voidnsContentSink::ProcessOfflineManifest(constnsAString&aManifestSpec){// Don't bother processing offline manifest for documents// without a docshellif(!mDocShell){return;}// If this document has been interecepted, let's skip the processing of the// manifest.if(nsContentUtils::IsControlledByServiceWorker(mDocument)){return;}// If the docshell's in private browsing mode, we don't want to do any// manifest processing.nsCOMPtr<nsILoadContext>loadContext=do_QueryInterface(mDocShell);if(loadContext->UsePrivateBrowsing()){return;}nsresultrv;// Grab the application cache the document was loaded from, if any.nsCOMPtr<nsIApplicationCache>applicationCache;nsCOMPtr<nsIApplicationCacheChannel>applicationCacheChannel=do_QueryInterface(mDocument->GetChannel());if(applicationCacheChannel){boolloadedFromApplicationCache;rv=applicationCacheChannel->GetLoadedFromApplicationCache(&loadedFromApplicationCache);if(NS_FAILED(rv)){return;}if(loadedFromApplicationCache){rv=applicationCacheChannel->GetApplicationCache(getter_AddRefs(applicationCache));if(NS_FAILED(rv)){return;}}}if(aManifestSpec.IsEmpty()&&!applicationCache){// Not loaded from an application cache, and no manifest// attribute. Nothing to do here.return;}CacheSelectionActionaction=CACHE_SELECTION_NONE;nsCOMPtr<nsIURI>manifestURI;if(aManifestSpec.IsEmpty()){action=CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST;}else{nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI),aManifestSpec,mDocument,mDocumentURI);if(!manifestURI){return;}// Documents must list a manifest from the same originrv=mDocument->NodePrincipal()->CheckMayLoad(manifestURI,true,false);if(NS_FAILED(rv)){action=CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST;}else{// Only continue if the document has permission to use offline APIs or// when preferences indicate to permit it automatically.if(!nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal())&&!nsContentUtils::MaybeAllowOfflineAppByDefault(mDocument->NodePrincipal())&&!nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal())){return;}boolfetchedWithHTTPGetOrEquiv=false;nsCOMPtr<nsIHttpChannel>httpChannel(do_QueryInterface(mDocument->GetChannel()));if(httpChannel){nsAutoCStringmethod;rv=httpChannel->GetRequestMethod(method);if(NS_SUCCEEDED(rv))fetchedWithHTTPGetOrEquiv=method.EqualsLiteral("GET");}rv=SelectDocAppCache(applicationCache,manifestURI,fetchedWithHTTPGetOrEquiv,&action);if(NS_FAILED(rv)){return;}}}if(action==CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST){rv=SelectDocAppCacheNoManifest(applicationCache,getter_AddRefs(manifestURI),&action);if(NS_FAILED(rv)){return;}}switch(action){caseCACHE_SELECTION_NONE:break;caseCACHE_SELECTION_UPDATE:{nsCOMPtr<nsIOfflineCacheUpdateService>updateService=do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);if(updateService){nsCOMPtr<nsIDOMDocument>domdoc=do_QueryInterface(mDocument);updateService->ScheduleOnDocumentStop(manifestURI,mDocumentURI,mDocument->NodePrincipal(),domdoc);}break;}caseCACHE_SELECTION_RELOAD:{// This situation occurs only for toplevel documents, see bottom// of SelectDocAppCache method.// The document has been loaded from a different offline cache group than// the manifest it refers to, i.e. this is a foreign entry, mark it as such // and force a reload to avoid loading it. The next attempt will not // choose it.applicationCacheChannel->MarkOfflineCacheEntryAsForeign();nsCOMPtr<nsIWebNavigation>webNav=do_QueryInterface(mDocShell);webNav->Stop(nsIWebNavigation::STOP_ALL);webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE);break;}default:NS_ASSERTION(false,"Cache selection algorithm didn't decide on proper action");break;}}voidnsContentSink::ScrollToRef(){mDocument->ScrollToRef();}voidnsContentSink::StartLayout(boolaIgnorePendingSheets){if(mLayoutStarted){// Nothing to do herereturn;}mDeferredLayoutStart=true;if(!aIgnorePendingSheets&&WaitForPendingSheets()){// Bail out; we'll start layout when the sheets loadreturn;}mDeferredLayoutStart=false;// Notify on all our content. If none of our presshells have started layout// yet it'll be a no-op except for updating our data structures, a la// UpdateChildCounts() (because we don't want to double-notify on whatever we// have right now). If some of them _have_ started layout, we want to make// sure to flush tags instead of just calling UpdateChildCounts() after we// loop over the shells.FlushTags();mLayoutStarted=true;mLastNotificationTime=PR_Now();mDocument->SetMayStartLayout(true);nsCOMPtr<nsIPresShell>shell=mDocument->GetShell();// Make sure we don't call Initialize() for a shell that has// already called it. This can happen when the layout frame for// an iframe is constructed *between* the Embed() call for the// docshell in the iframe, and the content sink's call to OpenBody().// (Bug 153815)if(shell&&!shell->DidInitialize()){nsRectr=shell->GetPresContext()->GetVisibleArea();nsCOMPtr<nsIPresShell>shellGrip=shell;nsresultrv=shell->Initialize(r.width,r.height);if(NS_FAILED(rv)){return;}}// If the document we are loading has a reference or it is a// frameset document, disable the scroll bars on the views.mDocument->SetScrollToRef(mDocument->GetDocumentURI());}voidnsContentSink::NotifyAppend(nsIContent*aContainer,uint32_taStartIndex){if(aContainer->GetUncomposedDoc()!=mDocument){// aContainer is not actually in our document anymore.... Just bail out of// here; notifying on our document for this append would be wrong.return;}mInNotification++;{// Scope so we call EndUpdate before we decrease mInNotificationMOZ_AUTO_DOC_UPDATE(mDocument,UPDATE_CONTENT_MODEL,!mBeganUpdate);nsNodeUtils::ContentAppended(aContainer,aContainer->GetChildAt(aStartIndex),aStartIndex);mLastNotificationTime=PR_Now();}mInNotification--;}NS_IMETHODIMPnsContentSink::Notify(nsITimer*timer){if(mParsing){// We shouldn't interfere with our normal DidProcessAToken logicmDroppedTimer=true;returnNS_OK;}if(WaitForPendingSheets()){mDeferredFlushTags=true;}else{FlushTags();// Now try and scroll to the reference// XXX Should we scroll unconditionally for history loads??ScrollToRef();}mNotificationTimer=nullptr;returnNS_OK;}boolnsContentSink::IsTimeToNotify(){if(!sNotifyOnTimer||!mLayoutStarted||!mBackoffCount||mInMonolithicContainer){returnfalse;}if(WaitForPendingSheets()){mDeferredFlushTags=true;returnfalse;}PRTimenow=PR_Now();int64_tinterval=GetNotificationInterval();int64_tdiff=now-mLastNotificationTime;if(diff>interval){mBackoffCount--;returntrue;}returnfalse;}nsresultnsContentSink::WillInterruptImpl(){nsresultresult=NS_OK;SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),SINK_TRACE_CALLS,("nsContentSink::WillInterrupt: this=%p",this));#ifndef SINK_NO_INCREMENTALif(WaitForPendingSheets()){mDeferredFlushTags=true;}elseif(sNotifyOnTimer&&mLayoutStarted){if(mBackoffCount&&!mInMonolithicContainer){int64_tnow=PR_Now();int64_tinterval=GetNotificationInterval();int64_tdiff=now-mLastNotificationTime;// If it's already time for us to have a notificationif(diff>interval||mDroppedTimer){mBackoffCount--;SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),SINK_TRACE_REFLOW,("nsContentSink::WillInterrupt: flushing tags since we've ""run out time; backoff count: %d",mBackoffCount));result=FlushTags();if(mDroppedTimer){ScrollToRef();mDroppedTimer=false;}}elseif(!mNotificationTimer){interval-=diff;int32_tdelay=interval;// Convert to millisecondsdelay/=PR_USEC_PER_MSEC;mNotificationTimer=do_CreateInstance("@mozilla.org/timer;1",&result);if(NS_SUCCEEDED(result)){SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),SINK_TRACE_REFLOW,("nsContentSink::WillInterrupt: setting up timer with ""delay %d",delay));result=mNotificationTimer->InitWithCallback(this,delay,nsITimer::TYPE_ONE_SHOT);if(NS_FAILED(result)){mNotificationTimer=nullptr;}}}}}else{SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),SINK_TRACE_REFLOW,("nsContentSink::WillInterrupt: flushing tags ""unconditionally"));result=FlushTags();}#endifmParsing=false;returnresult;}nsresultnsContentSink::WillResumeImpl(){SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),SINK_TRACE_CALLS,("nsContentSink::WillResume: this=%p",this));mParsing=true;returnNS_OK;}nsresultnsContentSink::DidProcessATokenImpl(){if(mRunsToCompletion||!mParser){returnNS_OK;}// Get the current user event timensIPresShell*shell=mDocument->GetShell();if(!shell){// If there's no pres shell in the document, return early since// we're not laying anything out here.returnNS_OK;}// Increase before comparing to gEventProbeRate++mDeflectedCount;// Check if there's a pending eventif(sPendingEventMode!=0&&!mHasPendingEvent&&(mDeflectedCount%sEventProbeRate)==0){nsViewManager*vm=shell->GetViewManager();NS_ENSURE_TRUE(vm,NS_ERROR_FAILURE);nsCOMPtr<nsIWidget>widget;vm->GetRootWidget(getter_AddRefs(widget));mHasPendingEvent=widget&&widget->HasPendingInputEvent();}if(mHasPendingEvent&&sPendingEventMode==2){returnNS_ERROR_HTMLPARSER_INTERRUPTED;}// Have we processed enough tokens to check time?if(!mHasPendingEvent&&mDeflectedCount<uint32_t(mDynamicLowerValue?sInteractiveDeflectCount:sPerfDeflectCount)){returnNS_OK;}mDeflectedCount=0;// Check if it's time to return to the main event loopif(PR_IntervalToMicroseconds(PR_IntervalNow())>mCurrentParseEndTime){returnNS_ERROR_HTMLPARSER_INTERRUPTED;}returnNS_OK;}//----------------------------------------------------------------------voidnsContentSink::FavorPerformanceHint(boolperfOverStarvation,uint32_tstarvationDelay){staticNS_DEFINE_CID(kAppShellCID,NS_APPSHELL_CID);nsCOMPtr<nsIAppShell>appShell=do_GetService(kAppShellCID);if(appShell)appShell->FavorPerformanceHint(perfOverStarvation,starvationDelay);}voidnsContentSink::BeginUpdate(nsIDocument*aDocument,nsUpdateTypeaUpdateType){// Remember nested updates from updates that we started.if(mInNotification>0&&mUpdatesInNotification<2){++mUpdatesInNotification;}// If we're in a script and we didn't do the notification,// something else in the script processing caused the// notification to occur. Since this could result in frame// creation, make sure we've flushed everything before we// continue.if(!mInNotification++){FlushTags();}}voidnsContentSink::EndUpdate(nsIDocument*aDocument,nsUpdateTypeaUpdateType){// If we're in a script and we didn't do the notification,// something else in the script processing caused the// notification to occur. Update our notion of how much// has been flushed to include any new content if ending// this update leaves us not inside a notification.if(!--mInNotification){UpdateChildCounts();}}voidnsContentSink::DidBuildModelImpl(boolaTerminated){if(mDocument){MOZ_ASSERT(aTerminated||mDocument->GetReadyStateEnum()==nsIDocument::READYSTATE_LOADING,"Bad readyState");mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE);}if(mScriptLoader){mScriptLoader->ParsingComplete(aTerminated);}if(!mDocument->HaveFiredDOMTitleChange()){mDocument->NotifyPossibleTitleChange(false);}// Cancel a timer if we had one out thereif(mNotificationTimer){SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),SINK_TRACE_REFLOW,("nsContentSink::DidBuildModel: canceling notification ""timeout"));mNotificationTimer->Cancel();mNotificationTimer=nullptr;}}voidnsContentSink::DropParserAndPerfHint(void){if(!mParser){// Make sure we don't unblock unload too many timesreturn;}// Ref. Bug 49115// Do this hack to make sure that the parser// doesn't get destroyed, accidently, before// the circularity, between sink & parser, is// actually broken.// Drop our reference to the parser to get rid of a circular// reference.RefPtr<nsParserBase>kungFuDeathGrip(mParser.forget());if(mDynamicLowerValue){// Reset the performance hint which was set to FALSE// when mDynamicLowerValue was set.FavorPerformanceHint(true,0);}if(!mRunsToCompletion){mDocument->UnblockOnload(true);}}boolnsContentSink::IsScriptExecutingImpl(){return!!mScriptLoader->GetCurrentScript();}nsresultnsContentSink::WillParseImpl(void){if(mRunsToCompletion||!mDocument){returnNS_OK;}nsIPresShell*shell=mDocument->GetShell();if(!shell){returnNS_OK;}uint32_tcurrentTime=PR_IntervalToMicroseconds(PR_IntervalNow());if(sEnablePerfMode==0){nsViewManager*vm=shell->GetViewManager();NS_ENSURE_TRUE(vm,NS_ERROR_FAILURE);uint32_tlastEventTime;vm->GetLastUserEventTime(lastEventTime);boolnewDynLower=mDocument->IsInBackgroundWindow()||((currentTime-mBeginLoadTime)>uint32_t(sInitialPerfTime)&&(currentTime-lastEventTime)<uint32_t(sInteractiveTime));if(mDynamicLowerValue!=newDynLower){FavorPerformanceHint(!newDynLower,0);mDynamicLowerValue=newDynLower;}}mDeflectedCount=0;mHasPendingEvent=false;mCurrentParseEndTime=currentTime+(mDynamicLowerValue?sInteractiveParseTime:sPerfParseTime);returnNS_OK;}voidnsContentSink::WillBuildModelImpl(){if(!mRunsToCompletion){mDocument->BlockOnload();mBeginLoadTime=PR_IntervalToMicroseconds(PR_IntervalNow());}mDocument->ResetScrolledToRefAlready();if(mProcessLinkHeaderEvent.get()){mProcessLinkHeaderEvent.Revoke();DoProcessLinkHeader();}}/* static */voidnsContentSink::NotifyDocElementCreated(nsIDocument*aDoc){nsCOMPtr<nsIObserverService>observerService=mozilla::services::GetObserverService();if(observerService){nsCOMPtr<nsIDOMDocument>domDoc=do_QueryInterface(aDoc);observerService->NotifyObservers(domDoc,"document-element-inserted",EmptyString().get());}nsContentUtils::DispatchChromeEvent(aDoc,aDoc,NS_LITERAL_STRING("DOMDocElementInserted"),true,false);}NS_IMETHODIMPnsContentSink::GetName(nsACString&aName){aName.AssignASCII("nsContentSink_timer");returnNS_OK;}NS_IMETHODIMPnsContentSink::SetName(constchar*aName){returnNS_ERROR_NOT_IMPLEMENTED;}